<?php
if (realpath(__FILE__) == realpath($_SERVER['SCRIPT_FILENAME'])) {exit('No Direct Access!');}

/*
 +-----------------------------------------------------------------------+
 | Copyright (c) 2009 , Ersin Güvenç                                     |
 | All rights reserved.                                                  |
 |                                                                       |
 | EasyRest Client Library v 1.1 beta                                    |
 | PHP VERSION 5                                                         |
 | LICENSE: GPL                                                          |
 |                                                                       |
 | Redistribution and use in source and binary forms, with or without    |
 | modification, are permitted provided that the following conditions    |
 | are met:                                                              |
 |                                                                       |
 | o Redistributions of source code must retain the above copyright      |
 |   notice, this list of conditions and the following disclaimer.       |
 | o Redistributions in binary form must reproduce the above copyright   |
 |   notice, this list of conditions and the following disclaimer in the |
 |   documentation and/or other materials provided with the distribution.|
 | o The names of the authors may not be used to endorse or promote      |
 |   products derived from this software without specific prior written  |
 |   permission.                                                         |
 |                                                                       |
 |  This program is free software: you can redistribute it and/or modify |
 |  it under the terms of the GNU General Public License as published by |
 |  the Free Software Foundation, either version 3 of the License, or    |
 |  (at your option) any later version.                                  |
 |                                                                       |
 |  This program is distributed in the hope that it will be useful,      |
 |  but WITHOUT ANY WARRANTY; without even the implied warranty of       |
 |  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        |
 |  GNU General Public License for more details.                         |
 |                                                                       |
 |  You should have received a copy of the GNU General Public License    |
 |  along with this program. If not, see <http://www.gnu.org/licenses/>  |
 |                                                                       |
 +-----------------------------------------------------------------------+
 | Author: Ersin Güvenç <eguvenc at gmail.com>                           |
 +-----------------------------------------------------------------------+
*/

/**
 * @category  WebServices
 * @package   EasyRest
 * @author    Ersin Güvenç <eguvenc@gmail.com>
 * @copyright 2009 Ersin Güvenç
 * @license   http://www.opensource.org/licenses/gpl-3.0.html GPL
 * @version   1.0
 * @filesource
 * @link      http://www.develturk.com
 * @see       
 * @since     1.0
 * 
 */
 
// EasyRest Client Class 
include ('restCommon.inc.php'); 

require (PEAR_LIB.'HTTP/Request.php');

require ('restXML.php');

/**
* HTTP Response Codes.
* @global response_codes
*/
$GLOBALS['response_codes'] = array(

    //[Informational 1xx] 
    '100'=>"Continue",
    '101'=>"Switching Protocols",

    //[Successful 2xx]
    '200'=>"OK",
    '201'=>"Created",
    '202'=>"Accepted",
    '203'=>"Non-Authoritative Information",
    '204'=>"No Content",
    '205'=>"Reset Content",
    '206'=>"Partial Content",

    //[Redirection 3xx]
    '300'=>"Multiple Choices",
    '301'=>"Moved Permanently",
    '302'=>"Found",
    '303'=>"See Other",
    '304'=>"Not Modified",
    '305'=>"Use Proxy",
    '306'=>"(Unused)",
    '307'=>"Temporary Redirect",

    //[Client Error 4xx]
    '400'=>"Bad Request",
    '401'=>"Unauthorized",
    '402'=>"Payment Required",
    '403'=>"Forbidden",
    '404'=>"Not Found",
    '405'=>"Method Not Allowed",
    '406'=>"Not Acceptable",
    '407'=>"Proxy Authentication Required",
    '408'=>"Request Timeout",
    '409'=>"Conflict",
    '410'=>"Gone",
    '411'=>"Length Required",
    '412'=>"Precondition Failed",
    '413'=>"Request Entity Too Large",
    '414'=>"Request-URI Too Long",
    '415'=>"Unsupported Media Type",
    '416'=>"Requested Range Not Satisfiable",
    '417'=>"Expectation Failed",

    //[Server Error 5xx]
    '500'=>"Internal Server Error",
    '501'=>"Not Implemented",
    '502'=>"Bad Gateway",
    '503'=>"Service Unavailable",
    '504'=>"Gateway Timeout",
    '505'=>"HTTP Version Not Supported",
  );

/**
* Client_Exception class
* @version 1.0
*/
Class Client_Exception extends Easyrest_Exception{} 

/**
* Rest_Service abstract class
* @version 1.0
*/
abstract class Rest_Service 
{ 
    /**
    * construct() to call and config objects
    * @version 1.0
    */
    abstract protected function __construct();
    
    /**
    * print all server predefined functions for learn
    * @version 1.0
    */
    abstract protected function index();
 
}

/**
* Client Config class
* set client config variables like url,apikey
* @version 1.0
*/
Class Config
{
    /**
    * store config vars into array
    * @var array 
    */              
    public $settings = array();

    /**
    * static object instance
    * @var object 
    */       
    private static $instance = NULL;

    /**
    * Disable construct function
    * @version 1.0
    */
    private function __construct()
    {
        global $config;          
        //* Set iv size it must be equal with server iv e.g: abc123+=
        $this->settings['iv'] = $config['common']['iv'];
    }
    
    /**
    * Get the instantiated static object
    * @version 1.0
    * @return object instance
    */
    static function getInstance()
    {
        /* If there is not already an instance of this class,
         instantiate one.*/
        if (self::$instance == NULL)
        {
            self::$instance = new Config;
        }
 
        return self::$instance;
    }
    
    /**                 
    * Set server url
    * @version 1.0
    * @param string $url
    */
    function debug($bool)
    {
        $this->settings['debug'] = $bool;
        
        //if($bool)
        //header("Content-Type: text/xml");
        /* echo "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"; */
    }
    
    /**                 
    * Set server url
    * @version 1.0
    * @param string $url
    */
    function uri($url)
    {
        $this->settings['uri'] = $url;
    }
    
    #  << security configuration part >> #
    
    /**
    * Client id
    * set client id for get to api and secret keys
    * @version 1.0
    * @param string $secretkey user secret key
    */
    function id($id)
    {
        $this->settings['id'] = $id; 
    }
    
    /**
    * Set secretkey
    * set your own static secretkey
    * it must be equal with server secretkey
    * @version 1.0
    * @param string $secretkey user secret key
    */
    function secretkey($secretkey='')
    {
        $this->settings['secretkey'] = $secretkey; 
    }
    
    /**
    * Set apikey and start encryption!
    * it must be equal with server apikey
    * @version 1.0
    * @param string $apikey the apikey that u produced
    */
    function apikey($apikey='')
    {
        $this->settings['apikey'] = $apikey;
        
        //start encrption..
        $this->settings['encrypted_apikey'] = $this->encryptApikey(); 
    }
    
    #  << security configuration part >>  #
    
    /**
    * Encrypt user apikey
    * Blowfish encrypt method
    * 
    * @access private
    * @version 1.0
    * @return mixed encrypted_apikey
    */ 
    private function encryptApikey()
    {
        require (PEAR_LIB.'Crypt/Blowfish.php');
        
        $blowfish =& Crypt_Blowfish::factory('cbc');

        if(PEAR::isError($blowfish))
        throw new Client_Exception($blowfish->getMessage());
        
        $config = self::getInstance();
        
        $secretkey = $config->item('secretkey');
        if(empty($secretkey)) throw new Client_Exception('Please set secret key!');
        
        $apikey    = $config->item('apikey');
        if(empty($apikey)) throw new Client_Exception('Please set api key!');
        
        $iv        = $config->item('iv');
        if(strlen($iv) < 8 || strlen($iv) > 8)
        throw new Client_Exception('Apikey iv length must be min and max 8 characters!');
        
        $blowfish->setKey($secretkey, $iv);
        $encrypted_apikey = $blowfish->encrypt($apikey);
        
        if (PEAR::isError($encrypted_apikey))
        throw new Client_Exception($encrypted_apikey->getMessage());
        
        return $encrypted_apikey;
    }
    
    /**
    * Get config item
    * 
    * @version 1.0
    * @param string $item
    */
    function item($item)
    {
        return $this->settings[$item];
    }

    
} //end class.

      
/**
* Client class
* 
* Build objects and remote all functions
* @version 1.0
*/
Class Client extends Rest_Service
{
    /**
    * class call object
    * @var object 
    */
    public $call;

    /**
    * class config object
    * @var object 
    */
    public $config;
                                             
     /**
    * Build objects
    * @version 1.0
    */
    function __construct()
    {
        $this->call   = Call::getInstance();
        $this->config = Config::getInstance();
    }   
    
    /**
    * Get current client library version
    * 
    * @version 1.0
    */
    function version(){ return "EasyRest Client v1.0";}     

    
    /**
    * Get all server predefined functions
    * 
    * @version 1.0
    * @return array 
    */
    function index()
    {
        $request = new http_request($this->config->item('uri'));
        
        $request->setMethod(HTTP_REQUEST_METHOD_POST);
        $request->addPostData("function", "index");
        
        $response = new Response($request);
        $data = $response->sendRequest();
         
        return $data;
    }
    
                                 
} //end class.
                              
/**
* Call class
* Build call command for every request
* run current command and get response
* @version 1.0
*/
Class Call
{
    /**                                                           
    * self static object instance
    * @var object
    */
    private static $instance;

    /**
    * requested command for current func. 
    * @var string
    */
    private $command = array();

    /**
    * requested params for current func.
    * @var array
    */
    private $params = array();

    /**
    * requested post variables
    * @var array
    */
    private $POST_vars = array();

    /**
    * requested get variables
    * @var array
    */
    private $GET_vars = array();

    /**
    * requested post variables
    * default method post. 
    * @var array
    */
    private $method = array(); 

    /**
    * config class object instance
    * @var object
    */
    public $config;

    /**
    * Disable construct function
    */
    function __construct(){}
    
    /**
    * Disable clone function
    */
    private function __clone(){}
    
    /**
    * Get the instantiated static object
    * 
    * @version 1.0
    * @return object instance
    */
    public static function getInstance()
    {
       if(! (self::$instance instanceof self))
       {
            self::$instance = new self();
       } 
       return self::$instance;
    }
    
    /**
    * Set current command
    * and build config class
    *  
    * @version 1.0
    * @param string $command
    */                          
    function command($command)
    {
        $this->config = Config::getInstance(); 
        $this->command[] = $command;
    }
    
    /**
    * @todo multiple call in one request
    * Will be in next version!
    */                                 
    function multiCommand(array $commands){}
    
    /**
    * Set params of current command
    * @version 1.0
    * @param array $params
    */
    function params($params)
    {
        $this->params[] = $params;
    }
    
    /**
    * Set minimal post method
    *
    * @version 1.0
    * @param array $POST_params
    * @todo add PUT and DELETE methods
    */
    function post($POST_params='')
    {
        $this->method[] = 'POST';
        if(!empty($POST_params))
        $this->POST_vars[] = $POST_params; 
    }
    
    /**
    * Set minimal get method
    *
    * @version 1.0
    * @param array $GET_params
    * @todo add PUT and DELETE methods
    */
    function get($GET_params='')
    {
        $this->method[] = 'GET';
        if(!empty($GET_params))
        $this->GET_vars[] = $GET_params;
    }
    
    
    /**
    * Run current functionS
    * 
    * @version 1.0
    * @uses self::doRequest send request function 
    * @uses self::reset after the request reset all variables
    * @return array $array user result.
    */
    function run()
    {
        /*
        print_r($this->command);
        print_r($this->method);
        print_r($this->POST_vars);
        print_r($this->GET_vars);
        print_r($this->params);
        exit;
        */
        try                               
        {   
            $array = $this->doRequest();
            
            //reset all variables.
            $this->reset();
        
        } catch (Client_Exception $e) {

            $array = array('fault' => 
                              array(
                                    'string' => $e->getMessage(),
                                    'line'   => $e->getLine())
                                    );
        } //end try.
        
        return $array; 
    }
    
    
    /**
    * Start do request
    * Send request to server
    * 
    * @version 1.0
    * @access private
    * @uses self::encryptApikey encrypt apikey by blowfish
    * @uses self::unserializeXMLResponse unserialize xml content
    * @return array 
    */  
    private function doRequest()
    {
        global $config;
        
        if(empty($this->command))
        throw new Client_Exception('Command cant\'t be empty!');
    
        if($this->command == 'index')
        return $this->index();
    
        $request = Request::getInstance();
        $request->setRequest(new http_request($this->config->item('uri')));
        
        $data['id']       = $this->config->item('id'); 
        $data['apikey']   = $this->config->item('encrypted_apikey');
        
        $response = array();
        
        //use all $this properties for once and save memory!!
        $commands  = $this->command;
        $params    = $this->params;
        $method    = $this->method;
        $GET_vars  = $this->GET_vars;
        $POST_vars = $this->POST_vars;
        
        $i = -1;
        foreach($commands as $key=>$val)
        {
            ++$i;
            switch($method[$i])
            {
                case 'GET':
                $data['get']   = $GET_vars;
                break;
                
                case 'POST':
                $data['post']  = $POST_vars;
                break;
                /*
                case 'PUT':
                break;
                
                case 'DELETE':
                break;
                
                case 'INSERT':
                break;,
                */
            }
            
            $data['function'] = $val;
            $data['params']   = $params[$i];
                               
            $response[] = $request->sendRequest($data);
            
        } //end foreach.
    
       //print_r($response); exit;
       
       $xml_content = "<?xml version=\"1.0\" encoding=\"";
       $xml_content.= $config['xml_serializer']['xml_encoding'];
       $xml_content.= "\"?>"."\n";
       $xml_content.= '<rest>';
       foreach($response as $k=>$value)
       {
            $xml_content.= $value;
       }
       $xml_content.= '</rest>';
       
       //result
       if($this->config->item('debug'))
       {
           header("Content-Type: text/xml");
           echo $xml_content;
           exit;
           
       } else 
       {                           
          $array = self::unserializeXMLResponse($xml_content);
          return $array;
       }
       
    } //end doRequest.
    
     /**
    * Unserializer Function (Convert XML to Array)
    * Parse XML response with XML_unserializer
    *  
    * @version 1.0
    * @param xml $response
    * @return array unserialized response 
    */
    static function unserializeXMLResponse($response)
    {   
        $xml = Rest_xml::getInstance();
        $data = $xml->unserialize($response);
        
        return $data;                                           
    } 
    
    /**
    * After the every request reset all variables
    * @access private
    * @version 1.0
    */
    private function reset()
    {
        $this->command   = array();
        $this->method    = array();
        $this->params    = array();
        $this->POST_vars = array();
        $this->GET_vars  = array();
    }
    
    
} //end class.


/**
* Request class
* Do a mono request with Call class.
* @version 1.0
*/
Class Request
{
    /**
    * requested post variables
    * @var array
    */
    public $request;

    /**
    * requested post variables
    * @var array
    */
    public $method;

    /**
    * object instance we have to declare it as static. 
    * @var object
    */ 
    static $instance;
   
   //this object can not copy except the getInstance() method.
    private function __clone() {}
   
   //prevent directly access we want acces this only from getInstance method.
    private function __construct(){}
   
     /**
    * Set pear http request object
    * @version 1.0
    */
    function setRequest(http_request $request)
    {
        $this->request = $request;
    }
   
    //lets prevent duplication of memory using by Singleton Pattern.
    public static function getInstance()
    {
       if(! (self::$instance instanceof self))
       {
            self::$instance = new self();
       } 
       return self::$instance;
    }

    /**
    * Set rest request method
    * 
    * @param string $method set rest method
    * @access public 
    * @version 1.0
    */                     
    function setMethod($method)
    {                    
        $this->method = strtoupper($method);
        switch ($this->method) 
        {
            case 'POST':                  
                $this->request->setMethod(HTTP_REQUEST_METHOD_POST);
                break;
                
            case 'GET':
                $this->request->setMethod(HTTP_REQUEST_METHOD_GET);
                break;
            /*
                case 'PUT':
                break;
                
                case 'DELETE':
                break;
                
                case 'INSERT':
                break;,
                */
        }
        
    } //end setMethod.
    
    /**
    * Send http request
    * 
    * @param array $data request options
    * @version 1.0
    * @return string $data response body
    */
    function sendRequest(array $data)
    {
        if(!isset($data['post']) && !isset($data['get']))
        throw new Client_Exception('Please define a method POST or GET!');
                                                 
        if(isset($data['post']))
        {  
            $this->setMethod('POST');
            
        } elseif(isset($data['get']))
        {
            $this->setMethod('GET');
        }

        if(!is_array($data['params']))
        throw new Client_Exception('Function parameters must be an array!'); 
        
        
        switch ($this->method) 
        {
            case 'POST':                              
                $this->request->addPostData("function", $data['function']);
                if(isset($data['params']))               
                $this->request->addPostData("params", $data['params']);
                
                $this->request->addPostData("id", $data['id']);
                $this->request->addPostData("apikey", $data['apikey']);
                
                if(sizeof($data['post'] > 0))
                {
                  foreach($data['post'] as $key=>$val)
                  $this->request->addPostData($key, $val);  
                }
                
                break;
                
            case 'GET':
                $this->request->addQueryString("function", $data['function']);
                if(isset($data['params'])) 
                $this->request->addQueryString("params", $data['params']);
                
                $this->request->addQueryString("id", $data['id']);
                $this->request->addQueryString("apikey", $data['apikey']);
                
                if(sizeof($data['get'] > 0))
                {
                  foreach($data['get'] as $key=>$val)
                  $this->request->addPostData($key, $val);  
                }
                
                break;
                
                /*
                case 'PUT':
                break;
                
                case 'DELETE':
                break;
                
                case 'INSERT':
                break;,
                */
        } //end switch.
        
        $response = Response::sendRequest($this->request);
        return $response;
        
    } //end send request
    
} //end class.

   
/**
* Response class
* Do response for every request
* @version 1.0
*/
Class Response
{  
    /**
    * Send http request
    * @version 1.0
    * @return string $response response body
    */
    public static function sendRequest(http_request $request)
    {   
        $request->sendRequest();
        if (PEAR::isError($request))
        {
            throw new Client_Exception($request->getMessage());
            
        }else{
              
            $code = $request->getResponseCode();
            if($code == '200')
            {
              $response = $request->getResponseBody();
            
            } else
            {
                
              $http_error = $GLOBALS['response_codes'][$code]; 
                
              throw new Client_Exception('No communication available with the server! 
              HTTP ERROR: '.$code. ' - '. $http_error);
            }
          
            return $response;
        }
        
    } //end func.


} //end class.
                       


?>
